package com.foohoo.benchmark.string;

import lombok.extern.slf4j.Slf4j;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 测试不同的字符串拼接方式的性能
 * Benchmark                               Mode  Cnt         Score          Error  Units
 * StringAppend.lessAppendByPlus           avgt    3     26441.252 ±     3594.806  ns/op
 * StringAppend.lessAppendByStringBuffer   avgt    3      2033.104 ±     1312.857  ns/op
 * StringAppend.lessAppendByStringBuilder  avgt    3      2032.192 ±      995.564  ns/op
 * StringAppend.lessBase                   avgt    3       524.551 ±      167.781  ns/op
 * StringAppend.manyAppendByPlus           avgt    3  21679837.433 ± 18600406.666  ns/op
 * StringAppend.manyAppendByStringBuffer   avgt    3     61823.160 ±    54462.710  ns/op
 * StringAppend.manyAppendByStringBuilder  avgt    3     61959.887 ±    38363.582  ns/op
 * StringAppend.manyBase                   avgt    3     16394.804 ±    10675.045  ns/op
 * StringAppend.onceAppendByPlus           avgt    3       133.062 ±       40.098  ns/op
 * StringAppend.onceAppendByStringBuffer   avgt    3       140.056 ±      120.018  ns/op
 * StringAppend.onceAppendByStringBuilder  avgt    3       136.578 ±       24.527  ns/op
 * StringAppend.onceBase                   avgt    3        40.421 ±        1.685  ns/op
 * 一句话结论: 一两次的场景下用String+即可, 因为创建高级对象会有额外损耗;循环场景下优先用StringBuilder, 如需保证线程安全需使用StringBuffer(锁会产生额外性能损耗)
 * 顺便, 单线程下jit会做一些优化，所以StringBuilder和StringBuffer的差异不明显
 *
 * @author mzdbxqh
 * @date 2020-10-20 11:03
 **/
@Slf4j
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 3, time = 1)
@Fork(1)
@Threads(1)
@State(Scope.Benchmark)
public class StringAppend {

	private static String[] strPool;

	private static Random random = new Random();

	private final static int POOL_SIZE = 1024;

	private final static int LESS_LOOP_TIME = 32;

	private final static int MANY_LOOP_TIME = 1024;

	@Setup
	public void initialize() {
		strPool = new String[POOL_SIZE];
		for (int i = 0; i < POOL_SIZE; i++) {
			strPool[i] = UUID.randomUUID().toString();
		}
	}

	@Benchmark
	public void onceBase(Blackhole bh) {
		String str1 = strPool[random.nextInt(POOL_SIZE)];
		String str2 = strPool[random.nextInt(POOL_SIZE)];
		bh.consume(str1);
		bh.consume(str2);
	}

	@Benchmark
	public void onceAppendByPlus(Blackhole bh) {
		String str = strPool[random.nextInt(POOL_SIZE)] + strPool[random.nextInt(POOL_SIZE)];
		bh.consume(str);
	}

	@Benchmark
	public void onceAppendByStringBuffer(Blackhole bh) {
		StringBuffer buffer = new StringBuffer();
		buffer.append(strPool[random.nextInt(POOL_SIZE)]);
		buffer.append(strPool[random.nextInt(POOL_SIZE)]);
		String result = buffer.toString();
		bh.consume(result);
	}

	@Benchmark
	public void onceAppendByStringBuilder(Blackhole bh) {
		StringBuilder builder = new StringBuilder();
		builder.append(strPool[random.nextInt(POOL_SIZE)]);
		builder.append(strPool[random.nextInt(POOL_SIZE)]);
		String result = builder.toString();
		bh.consume(result);
	}

	@Benchmark
	public void lessBase(Blackhole bh) {
		for (int i = 0; i < LESS_LOOP_TIME; i++) {
			String str = strPool[random.nextInt(POOL_SIZE)];
			bh.consume(str);
		}
	}

	@Benchmark
	public void lessAppendByPlus(Blackhole bh) {
		String str = "";
		for (int i = 0; i < LESS_LOOP_TIME; i++) {
			str += strPool[random.nextInt(POOL_SIZE)];
		}
		bh.consume(str);
	}

	@Benchmark
	public void lessAppendByStringBuffer(Blackhole bh) {
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < LESS_LOOP_TIME; i++) {
			buffer.append(strPool[random.nextInt(POOL_SIZE)]);
		}
		String result = buffer.toString();
		bh.consume(result);
	}

	@Benchmark
	public void lessAppendByStringBuilder(Blackhole bh) {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < LESS_LOOP_TIME; i++) {
			builder.append(strPool[random.nextInt(POOL_SIZE)]);
		}
		String result = builder.toString();
		bh.consume(result);
	}

	@Benchmark
	public void manyBase(Blackhole bh) {
		for (int i = 0; i < MANY_LOOP_TIME; i++) {
			String str = strPool[random.nextInt(POOL_SIZE)];
			bh.consume(str);
		}
	}

	@Benchmark
	public void manyAppendByPlus(Blackhole bh) {
		String str = "";
		for (int i = 0; i < MANY_LOOP_TIME; i++) {
			str += strPool[random.nextInt(POOL_SIZE)];
		}
		bh.consume(str);
	}

	@Benchmark
	public void manyAppendByStringBuffer(Blackhole bh) {
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < MANY_LOOP_TIME; i++) {
			buffer.append(strPool[random.nextInt(POOL_SIZE)]);
		}
		String result = buffer.toString();
		bh.consume(result);
	}

	@Benchmark
	public void manyAppendByStringBuilder(Blackhole bh) {
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < MANY_LOOP_TIME; i++) {
			builder.append(strPool[random.nextInt(POOL_SIZE)]);
		}
		String result = builder.toString();
		bh.consume(result);
	}
}
